/*! ******************************************************************************
 *
 * Created on 2021年11月29日
 *
 * Copyright(c) YuZhou Big Data Laboratory CO.,Ltd, 2022.  All rights reserved.
 *
 *******************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ******************************************************************************/
package org.pentaho.di.ui.spoon.navigation.view;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MenuDetectEvent;
import org.eclipse.swt.events.MenuDetectListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.pentaho.di.cluster.ClusterSchema;
import org.pentaho.di.cluster.SlaveServer;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.database.DatabaseMeta;
import org.pentaho.di.core.dnd.DragAndDropContainer;
import org.pentaho.di.core.dnd.XMLTransfer;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.extension.ExtensionPointHandler;
import org.pentaho.di.core.extension.KettleExtensionPoint;
import org.pentaho.di.core.plugins.PluginInterface;
import org.pentaho.di.core.plugins.PluginTypeInterface;
import org.pentaho.di.core.plugins.StepPluginType;
import org.yzbdl.lanius.util.LogUtil;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.job.JobMeta;
import org.pentaho.di.job.entry.JobEntryCopy;
import org.pentaho.di.partition.PartitionSchema;
import org.pentaho.di.shared.SharedObjects;
import org.pentaho.di.trans.HasSlaveServersInterface;
import org.pentaho.di.trans.TransHopMeta;
import org.pentaho.di.trans.TransMeta;
import org.pentaho.di.trans.step.StepMeta;
import org.pentaho.di.ui.core.ConstUI;
import org.pentaho.di.ui.core.dialog.ErrorDialog;
import org.pentaho.di.ui.core.widget.TreeMemory;
import org.pentaho.di.ui.spoon.MainSpoonPerspective;
import org.pentaho.di.ui.spoon.Spoon;
import org.pentaho.di.ui.spoon.TabMapEntry;
import org.pentaho.di.ui.spoon.TreeSelection;
import org.pentaho.di.ui.spoon.navigation.view.category.JobCategory;
import org.pentaho.di.ui.spoon.navigation.view.category.TransCategory;
import org.pentaho.di.ui.spoon.dialog.MetaStoreExplorerDialog;
import org.pentaho.di.ui.spoon.job.JobGraph;
import org.pentaho.di.ui.spoon.trans.TransGraph;
import org.pentaho.ui.xul.XulComponent;
import org.pentaho.ui.xul.components.XulMenuitem;
import org.pentaho.ui.xul.containers.XulMenupopup;

import com.google.common.annotations.VisibleForTesting;

/**
 * 主对象树，负责整个主对象树的创建、销毁、更新等.
 *
 * @author hs@yzbdl.ac.cn
 */
public class ViewTree {
    private static Class<?> PKG = Spoon.class;

    private Spoon spoon;

    private Tree selectionTree;

    private Map<String, XulComponent> menuMap = new HashMap<>();

    private boolean shown = true;

    private Composite container;

    public ViewTree(Spoon spoon, FormData layout) {
        super();
        this.spoon = spoon;
        container = new Composite(spoon.getLeftTreeComposite(), SWT.PUSH);
        container.setLayout(new FillLayout(SWT.VERTICAL));
        container.setLayoutData(layout);
        container.setVisible(true);

        selectionTree = new Tree(container, SWT.SINGLE);
        selectionTree.setVisible(true);
        spoon.props.setLook(selectionTree);
        selectionTree.setLayout(new FillLayout());

        addKeyListener();
        addMenuDetectListener();
        addSelectionListener();
        addTreeMemoryListener();
		addDragSourceToTree();

        container.layout();
    }

    /**
     * 添加键盘事件 , 监听ctrl+F5事件, 打开MetaStoreExplorer.
     */
    private void addKeyListener() {
        selectionTree.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.keyCode == SWT.F5 && (e.stateMask & SWT.CONTROL) != 0) {
                    new MetaStoreExplorerDialog(spoon.getShell(), spoon.metaStore).open();
                }
            }
        });
    }

    /**
     * 添加树选中事件
     */
    private void addSelectionListener() {
        selectionTree.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                showSelection();
            }

            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
                doubleClickedInTree(selectionTree, false);
            }
        });
    }

    /**
     * 添加右键菜单事件, 右键打开菜单
     */
    private void addMenuDetectListener() {
        selectionTree.addMenuDetectListener(new MenuDetectListener() {
            @Override
            public void menuDetected(MenuDetectEvent e) {
                setMenu(selectionTree);
            }
        });
    }

    /**
     * 添加树记忆事件, 记录树的打开关闭状态.
     */
    private void addTreeMemoryListener() {
        TreeMemory.addTreeListener(selectionTree, BaseMessages.getString(PKG, "Spoon.MainTree.Label"));
    }

    /**
     * 设置树右键菜单
     *
     * @param tree
     */
    private synchronized void setMenu(Tree tree) {
        TreeSelection[] objects = spoon.delegates.tree.getViewTreeSelection(tree);
        if (objects.length != 1) {
            return;
        }

        TreeSelection object = objects[0];
        spoon.selectionObject = object.getSelection();
        spoon.selectionObjectParent = object.getParent();

        Object selection = object.getSelection();
        // Not clicked on a real object: returns a class
        XulMenupopup spoonMenu = null;
        if (selection instanceof Class<?>) {
            if (selection.equals(TransMeta.class)) {
                // 新建转换右键菜单
                spoonMenu = (XulMenupopup) getComponent("trans-class");
            } else if (selection.equals(JobMeta.class)) {
                // 作业右键菜单
                spoonMenu = (XulMenupopup) getComponent("job-class");
            } else if (selection.equals(TransHopMeta.class)) {
                // 新建节点连接
                spoonMenu = (XulMenupopup) getComponent("trans-hop-class");
            } else if (selection.equals(DatabaseMeta.class)) {
                // DB连接右键菜单
                spoonMenu = (XulMenupopup) getComponent("database-class");
            } else if (selection.equals(PartitionSchema.class)) {
                // 数据库分区右键菜单
                spoonMenu = (XulMenupopup) getComponent("partition-schema-class");
            } else if (selection.equals(ClusterSchema.class)) {
                // kettle集群模式右键菜单
                spoonMenu = (XulMenupopup) getComponent("cluster-schema-class");
            } else if (selection.equals(SlaveServer.class)) {
                // 子服务器
                spoonMenu = (XulMenupopup) getComponent("slave-cluster-class");
            } else {
                spoonMenu = null;
            }
        } else {
            if (selection instanceof TransMeta) {
                // 转换实例右键菜单
                spoonMenu = (XulMenupopup) getComponent("trans-inst");
            } else if (selection instanceof JobMeta) {
                // 作业实例右键菜单
                spoonMenu = (XulMenupopup) getComponent("job-inst");
            } else if (selection instanceof PluginInterface) {
                //
                spoonMenu = (XulMenupopup) getComponent("step-plugin");
            } else if (selection instanceof DatabaseMeta) {
                spoonMenu = (XulMenupopup) getComponent("database-inst");
                // disable for now if the connection is an SAP ERP type of
                // database...
                XulMenuitem item = (XulMenuitem) getComponent("database-inst-explore");
                if (item != null) {
                    final DatabaseMeta databaseMeta = (DatabaseMeta) selection;
                    item.setDisabled(!databaseMeta.isExplorable());
                }
                item = (XulMenuitem) getComponent("database-inst-clear-cache");
                if (item != null) {
                    final DatabaseMeta databaseMeta = (DatabaseMeta) spoon.getSelectionObject();
                    item.setLabel(BaseMessages.getString(PKG, "Spoon.Menu.Popup.CONNECTIONS.ClearDBCache")
                            + databaseMeta.getName()); // Clear
                }
                item = (XulMenuitem) getComponent("database-inst-share");
                if (item != null) {
                    final DatabaseMeta databaseMeta = (DatabaseMeta) selection;
                    if (databaseMeta.isShared()) {
                        item.setLabel(BaseMessages.getString(PKG, "Spoon.Menu.Popup.CONNECTIONS.UnShare"));
                    } else {
                        item.setLabel(BaseMessages.getString(PKG, "Spoon.Menu.Popup.CONNECTIONS.Share"));
                    }
                }
            } else if (selection instanceof StepMeta) {
                spoonMenu = (XulMenupopup) getComponent("step-inst");
            } else if (selection instanceof JobEntryCopy) {
                spoonMenu = (XulMenupopup) getComponent("job-entry-copy-inst");
            } else if (selection instanceof TransHopMeta) {
                spoonMenu = (XulMenupopup) getComponent("trans-hop-inst");
            } else if (selection instanceof PartitionSchema) {
                spoonMenu = (XulMenupopup) getComponent("partition-schema-inst");
            } else if (selection instanceof ClusterSchema) {
                spoonMenu = (XulMenupopup) getComponent("cluster-schema-inst");
            } else if (selection instanceof SlaveServer) {
                spoonMenu = (XulMenupopup) getComponent("slave-server-inst");
            }
        }
        if (spoonMenu != null) {
            ConstUI.displayMenu(spoonMenu, tree);
        } else {
            tree.setMenu(null);
        }
        createPopUpMenuExtension();
    }

    @VisibleForTesting
    public void createPopUpMenuExtension() {
        try {
            ExtensionPointHandler.callExtensionPoint(spoon.getLog(), KettleExtensionPoint.SpoonPopupMenuExtension.id,
                    selectionTree);
        } catch (Exception e) {
            LogUtil.error("Error handling menu right click on job entry through extension point", e);
        }
    }

    private XulComponent getComponent(String menuId) {
        XulComponent menu = menuMap.get(menuId);
        if (menu == null) {
            menu = spoon.getMainSpoonContainer().getDocumentRoot().getElementById(menuId);
            menuMap.put(menuId, menu);
        }
        return menu;
    }

    /**
     * 点击主对象树上的转换/作业，打开核心对象树
     */
    private void showSelection() {
        //更新分组排序
//        Spoon spoon = Spoon.getInstance();
//        spoon.syncPluginCategoryOrder();
        // 获取选中的主对象
        TreeSelection[] objects = spoon.delegates.tree.getViewTreeSelection(selectionTree);
        if (objects.length != 1) {
            return;
        }

        TreeSelection object = objects[0];

        final Object selection = object.getSelection();
        final Object parent = object.getParent();

        TransMeta transMeta = null;
        if (selection instanceof TransMeta) {
            transMeta = (TransMeta) selection;
        }
        if (parent instanceof TransMeta) {
            transMeta = (TransMeta) parent;
        }

        if (transMeta != null) {
            TabMapEntry entry = spoon.delegates.tabs.findTabMapEntry(transMeta);
            if (entry != null) {
                int current = spoon.tabfolder.getSelectedIndex();
                int desired = spoon.tabfolder.indexOf(entry.getTabItem());
                if (current != desired) {
                    spoon.tabfolder.setSelected(desired);
                }
                transMeta.setInternalKettleVariables();
                if (spoon.getCoreObjectsState() != Spoon.STATE_CORE_OBJECTS_SPOON) {
                    // Switch the core objects in the lower left corner to the
                    // spoon trans types
                    spoon.refreshDesignTree();
                }
            }
        }

        JobMeta jobMeta = null;
        if (selection instanceof JobMeta) {
            jobMeta = (JobMeta) selection;
        }
        if (parent instanceof JobMeta) {
            jobMeta = (JobMeta) parent;
        }
        if (jobMeta != null) {

            TabMapEntry entry = spoon.delegates.tabs.findTabMapEntry(jobMeta);
            if (entry != null) {
                int current = spoon.tabfolder.getSelectedIndex();
                int desired = spoon.tabfolder.indexOf(entry.getTabItem());
                if (current != desired) {
                    spoon.tabfolder.setSelected(desired);
                }
                jobMeta.setInternalKettleVariables();
                if (spoon.getCoreObjectsState() != Spoon.STATE_CORE_OBJECTS_CHEF) {
                    // Switch the core objects in the lower left corner to the
                    // spoon job types
                    //
                    spoon.refreshDesignTree();
                }
            }
        }
    }

    public void addDragSourceToTree() {
        // Drag & Drop for steps
        Transfer[] ttypes = new Transfer[]{XMLTransfer.getInstance()};

        DragSource ddSource = new DragSource(selectionTree, DND.DROP_MOVE);
        ddSource.setTransfer(ttypes);
        ddSource.addDragListener(new DragSourceListener() {
            public void dragStart(DragSourceEvent event) {
                TreeSelection[] treeObjects = spoon.delegates.tree.getViewTreeSelection(selectionTree);
                if (treeObjects.length == 0) {
                    event.doit = false;
                    return;
                }

                TreeSelection treeObject = treeObjects[0];
                Object object = treeObject.getSelection();
                TransMeta transMeta = spoon.getActiveTransformation();
                // JobMeta jobMeta = spoon.getActiveJob();

                if (object instanceof StepMeta || object instanceof PluginInterface
                        || (object instanceof DatabaseMeta && transMeta != null) || object instanceof TransHopMeta
                        || object instanceof JobEntryCopy) {
                    event.doit = true;
                } else {
                    event.doit = false;
                }
            }

            public void dragSetData(DragSourceEvent event) {
                TreeSelection[] treeObjects = spoon.delegates.tree.getViewTreeSelection(selectionTree);
                if (treeObjects.length == 0) {
                    event.doit = false;
                    return;
                }

                int type = 0;
                String data = null;

                TreeSelection treeObject = treeObjects[0];
                Object object = treeObject.getSelection();

                if (object instanceof StepMeta) {
                    StepMeta stepMeta = (StepMeta) object;
                    type = DragAndDropContainer.TYPE_STEP;
                    data = stepMeta.getName(); // name of the step.
                } else if (object instanceof PluginInterface) {
                    PluginInterface plugin = (PluginInterface) object;
                    Class<? extends PluginTypeInterface> pluginType = plugin.getPluginType();
                    if (Const.classIsOrExtends(pluginType, StepPluginType.class)) {
                        type = DragAndDropContainer.TYPE_BASE_STEP_TYPE;
                        data = plugin.getName(); // Step type name
                    } else {
                        type = DragAndDropContainer.TYPE_BASE_JOB_ENTRY;
                        data = plugin.getName(); // job entry type name
                        if (treeObject.getItemText().equals(JobMeta.createStartEntry().getName())) {
                            data = treeObject.getItemText();
                        } else if (treeObject.getItemText().equals(JobMeta.createDummyEntry().getName())) {
                            data = treeObject.getItemText();
                        }
                    }
                } else if (object instanceof DatabaseMeta) {
                    DatabaseMeta databaseMeta = (DatabaseMeta) object;
                    type = DragAndDropContainer.TYPE_DATABASE_CONNECTION;
                    data = databaseMeta.getName();
                } else if (object instanceof TransHopMeta) {
                    TransHopMeta hop = (TransHopMeta) object;
                    type = DragAndDropContainer.TYPE_TRANS_HOP;
                    data = hop.toString(); // nothing for really ;-)
                } else if (object instanceof JobEntryCopy) {
                    JobEntryCopy jobEntryCopy = (JobEntryCopy) object;
                    type = DragAndDropContainer.TYPE_JOB_ENTRY;
                    data = jobEntryCopy.getName(); // name of the job entry.
                } else {
                    event.doit = false;
                    return; // ignore anything else you drag.
                }

                event.data = new DragAndDropContainer(type, data);
            }

            public void dragFinished(DragSourceEvent event) {
            }
        });

    }

    /**
     * 响应主对象树鼠标双击时间
     *
     * @param tree  主对象树
     * @param shift 是否上档
     * @author hs@yzbdl.ac.cn
     */
    private void doubleClickedInTree(Tree tree, boolean shift) {
        //双击主对象树节点时分组排序
        Spoon spoon = Spoon.getInstance();
        spoon.syncPluginCategoryOrder();
        TreeSelection[] objects = spoon.delegates.tree.getViewTreeSelection(tree);
        if (objects.length != 1) {
            return;
        }

        TreeSelection object = objects[0];

        final Object selection = object.getSelection();
        final Object parent = object.getParent();

        if (selection instanceof Class<?>) {
            if (selection.equals(TransMeta.class)) {
                newTransFile();
            }
            if (selection.equals(JobMeta.class)) {
                newJobFile();
            }
            if (selection.equals(TransHopMeta.class)) {
                newHop((TransMeta) parent);
            }
            if (selection.equals(DatabaseMeta.class)) {
                spoon.delegates.db.newConnection();
            }
            if (selection.equals(PartitionSchema.class)) {
                spoon.newPartitioningSchema((TransMeta) parent);
            }
            if (selection.equals(ClusterSchema.class)) {
                spoon.newClusteringSchema((TransMeta) parent);
            }
            if (selection.equals(SlaveServer.class)) {
                spoon.newSlaveServer((HasSlaveServersInterface) parent);
            }
        } else {
            if (selection instanceof TransMeta) {
                TransGraph.editProperties((TransMeta) selection, spoon, spoon.rep, true);
            }
            if (selection instanceof JobMeta) {
                JobGraph.editProperties((JobMeta) selection, spoon, spoon.rep, true);
            }
            // if (selection instanceof PluginInterface) {
            // PluginInterface plugin = (PluginInterface) selection;
            // if (plugin.getPluginType().equals(StepPluginType.class)) {
            // TransGraph transGraph = spoon.getActiveTransGraph();
            // if (transGraph != null) {
            // transGraph.addStepToChain(plugin, shift);
            // }
            // }
            // if (plugin.getPluginType().equals(JobEntryPluginType.class)) {
            // JobGraph jobGraph = spoon.getActiveJobGraph();
            // if (jobGraph != null) {
            // jobGraph.addJobEntryToChain(object.getItemText(), shift);
            // }
            // }
            // }
            if (selection instanceof DatabaseMeta) {
                DatabaseMeta database = (DatabaseMeta) selection;
                spoon.delegates.db.editConnection(database);
            }
            if (selection instanceof StepMeta) {
                StepMeta step = (StepMeta) selection;
                spoon.delegates.steps.editStep((TransMeta) parent, step);
                spoon.sharedObjectSyncUtil.synchronizeSteps(step);
            }
            if (selection instanceof JobEntryCopy) {
                spoon.editJobEntry((JobMeta) parent, (JobEntryCopy) selection);
            }
            if (selection instanceof TransHopMeta) {
                spoon.editHop((TransMeta) parent, (TransHopMeta) selection);
            }
            if (selection instanceof PartitionSchema) {
                spoon.editPartitionSchema((TransMeta) parent, (PartitionSchema) selection);
            }
            if (selection instanceof ClusterSchema) {
                spoon.delegates.clusters.editClusterSchema((TransMeta) parent, (ClusterSchema) selection);
            }
            if (selection instanceof SlaveServer) {
                spoon.editSlaveServer((SlaveServer) selection);
            }

            spoon.editSelectionTreeExtension(selection);
        }
    }

    public void newTransFile() {
        TransMeta transMeta = new TransMeta();
        transMeta.addObserver(spoon);
        /*
         * Set the variables that were previously defined in this session on the
         * transformation metadata too.
         */
        spoon.setTransMetaVariables(transMeta);

        // Pass repository information
        //
        transMeta.setRepository(spoon.rep);
        transMeta.setMetaStore(spoon.metaStore);

        try {
            SharedObjects sharedObjects = spoon.rep != null ? spoon.rep.readTransSharedObjects(transMeta)
                    : transMeta.readSharedObjects();
            spoon.sharedObjectsFileMap.put(sharedObjects.getFilename(), sharedObjects);
            if (spoon.rep == null) {
                transMeta.setSharedObjects(sharedObjects);
            }
            transMeta.importFromMetaStore();
            transMeta.clearChanged();
        } catch (Exception e) {
            new ErrorDialog(spoon.getShell(),
                    BaseMessages.getString(PKG, "Spoon.Exception.ErrorReadingSharedObjects.Title"),
                    BaseMessages.getString(PKG, "Spoon.Exception.ErrorReadingSharedObjects.Message"), e);
        }

        // Set the location of the new transMeta to that of the default location
        // or the last saved location
        transMeta.setRepositoryDirectory(spoon.getDefaultSaveLocation(transMeta));

        int nr = 1;
        transMeta.setName(Spoon.STRING_TRANSFORMATION + " " + nr);

        // See if a transformation with the same name isn't already loaded...
        //
        while (spoon.findTransformation(spoon.delegates.tabs.makeTabName(transMeta, false)) != null) {
            nr++;
            transMeta.setName(Spoon.STRING_TRANSFORMATION + " " + nr); // rename
        }
        spoon.addTransGraph(transMeta);
        spoon.applyVariables();

        // switch to design mode...
        //
        if (spoon.setDesignMode()) {
            // No refresh done yet, do so
            refreshViewTree();
        }
        spoon.loadPerspective(MainSpoonPerspective.ID);

        try {
            ExtensionPointHandler.callExtensionPoint(spoon.getLog(), KettleExtensionPoint.TransformationCreateNew.id,
                    transMeta);
        } catch (KettleException e) {
            LogUtil.error("Failed to call extension point", e);
        }
    }

    public void newJobFile() {
        try {
            JobMeta jobMeta = new JobMeta();
            jobMeta.addObserver(spoon);

            // Set the variables that were previously defined in this session on
            // the transformation metadata too.
            //
            spoon.setJobMetaVariables(jobMeta);

            // Pass repository information
            //
            jobMeta.setRepository(spoon.rep);
            jobMeta.setMetaStore(spoon.metaStore);

            try {
                // MAKE LIKE TRANS
                SharedObjects sharedObjects = spoon.rep != null ? spoon.rep.readJobMetaSharedObjects(jobMeta)
                        : jobMeta.readSharedObjects();
                spoon.sharedObjectsFileMap.put(sharedObjects.getFilename(), sharedObjects);
                if (spoon.rep == null) {
                    jobMeta.setSharedObjects(sharedObjects);
                }
                jobMeta.importFromMetaStore();
            } catch (Exception e) {
                new ErrorDialog(spoon.getShell(),
                        BaseMessages.getString(PKG, "Spoon.Dialog.ErrorReadingSharedObjects.Title"),
                        BaseMessages.getString(PKG, "Spoon.Dialog.ErrorReadingSharedObjects.Message",
                                spoon.delegates.tabs.makeTabName(jobMeta, true)),
                        e);
            }

            // Set the location of the new jobMeta to that of the default
            // location or the last saved location
            jobMeta.setRepositoryDirectory(spoon.getDefaultSaveLocation(jobMeta));

            int nr = 1;
            jobMeta.setName(Spoon.STRING_JOB + " " + nr);

            // See if a transformation with the same name isn't already
            // loaded...
            while (spoon.findJob(spoon.delegates.tabs.makeTabName(jobMeta, false)) != null) {
                nr++;
                jobMeta.setName(Spoon.STRING_JOB + " " + nr); // rename
            }

            jobMeta.clearChanged();

            spoon.addJobGraph(jobMeta);
            spoon.applyVariables();

            // switch to design mode...
            //
            if (spoon.setDesignMode()) {
                // No refresh done yet, do so
                refreshViewTree();
            }
            spoon.loadPerspective(MainSpoonPerspective.ID);
        } catch (Exception e) {
            new ErrorDialog(spoon.getShell(), BaseMessages.getString(PKG, "Spoon.Exception.ErrorCreatingNewJob.Title"),
                    BaseMessages.getString(PKG, "Spoon.Exception.ErrorCreatingNewJob.Message"), e);
        }
    }

    public void newHop() {
        newHop((TransMeta) spoon.selectionObjectParent);
    }

    public void newHop(TransMeta transMeta) {
        spoon.newHop(transMeta, null, null);
    }

    /**
     * Refresh the object selection tree (on the left of the screen)
     */
    public void refreshViewTree() {
        // 获取已打开的转换
        TransMeta activeTransMeta = spoon.getActiveTransformation();
        // 获取已打开的作业
        JobMeta activeJobMeta = spoon.getActiveJob();

        // 当没有激活(打开中)的转换和作业时,显示所有
        boolean showAll = (activeTransMeta == null && activeJobMeta == null);

        // First remove the old ones.
        selectionTree.removeAll();

        // Now add the data back
        // 非仅显示激活转换/作业 --或-- 需要全部显示 --或 -- 转换激活中
        if (!spoon.props.isOnlyActiveFileShownInTree() || showAll || activeTransMeta != null) {
            CategoryTree tiTrans = new TransCategory(spoon, selectionTree);
            tiTrans.buildMetaTree();
        }

        if (!spoon.props.isOnlyActiveFileShownInTree() || showAll || activeJobMeta != null) {
            CategoryTree tiJobs = new JobCategory(spoon, selectionTree);
            tiJobs.buildMetaTree();
        }

        // Set the expanded state of the complete tree.
        TreeMemory.setExpandedFromMemory(selectionTree, Spoon.STRING_SPOON_MAIN_TREE);
        // refreshCoreObjectsHistory();
        // selectionTree.setFocus();
        selectionTree.layout();
        spoon.setShellText();
    }

    public boolean isShown() {
        return shown;
    }

    public void setShown(boolean shown) {
        this.shown = shown;
    }

    /**
     * 展开所有
     */
    public void expandAll() {
        this.tidyTree(this.selectionTree, true);
    }

    /**
     * 折叠所有
     */
    public void collapseAll() {
        this.tidyTree(this.selectionTree, false);
    }

    private void tidyTree(Tree tree, boolean expand) {
        if (tree != null && !tree.isDisposed()) {
            tidyBranches(tree.getItems(), expand);
        }
    }

    private void tidyBranches(TreeItem[] items, boolean expand) {
        for (TreeItem item : items) {
            item.setExpanded(expand);
            tidyBranches(item.getItems(), expand);
        }
    }

    public Tree getSelectionTree() {
        return selectionTree;
    }

    /**
     * @param showMainTree
     */
    public void setVisible(boolean showMainTree) {
        container.setVisible(showMainTree);
    }

}
